<?php


namespace apps\marriage\core\logic;

require EXTEND_PATH . "vm/org/wechat-sdk/autoload.php";
use Gaoming13\WechatPhpSdk\Wechat;
use Gaoming13\WechatPhpSdk\Api;
use think\Exception;
use think\facade\Log;
use vm\org\weixin\WXBizMsgCrypt;

class WeixinMpLogic
{
    const WECHAT_TOKEN_CACHE_KEY = 'wechat_mp_token';
    private $appId = '';
    private $appSecret = '';
    private $appToken = '';
    private $appEncodeAesKey = '';
    private $apiClient;
    private $wechatClient;

    public function __construct()
    {
        $siteConfigService = service('SiteConfig', SERVICE_NAMESPACE);
        $this->appId = $siteConfigService->getValueByCode('weixin_mp_app_id');
        $this->appSecret = $siteConfigService->getValueByCode('weixin_mp_app_secret');
        $this->appToken = $siteConfigService->getValueByCode('weixin_mp_app_token');
        $this->appEncodeAesKey = $siteConfigService->getValueByCode('weixin_mp_encode_aeskey');

        // wechat模块 - 处理用户发送的消息和回复消息
        $this->wechatClient = new \Gaoming13\WechatPhpSdk\Wechat(array(
            'appId' => $this->appId,
            'token' => $this->appToken,
            'encodingAESKey' => $this->appEncodeAesKey //可选
        ));
        // api模块 - 包含各种系统主动发起的功能
        $this->apiClient = new \Gaoming13\WechatPhpSdk\Api(
            array(
                'appId' => $this->appId,
                'appSecret' => $this->appSecret,
                'get_access_token' => function(){
                    // 用户需要自己实现access_token的返回
                    return cache(self::WECHAT_TOKEN_CACHE_KEY);
                },
                'save_access_token' => function($token) {
                    // 用户需要自己实现access_token的保存
                    cache(self::WECHAT_TOKEN_CACHE_KEY, $token);
                }
            )
        );
    }

    public function init(array $cfg)
    {
//        $siteConfigService = service('SiteConfig', SERVICE_NAMESPACE);
//        $this->appId = $siteConfigService->getValueByCode('weixin_mp_app_id');
//        $this->appSecret = $siteConfigService->getValueByCode('weixin_mp_app_secret');
//        $this->appToken = $siteConfigService->getValueByCode('weixin_mp_app_token');
//        $this->appEncodeAesKey = $siteConfigService->getValueByCode('weixin_mp_encode_aeskey');

        $this->appId = $cfg['app_id'];
        $this->appSecret = $cfg['app_secret'];
        $this->appToken = $cfg['app_token'];
        $this->appEncodeAesKey = $cfg['encode_aeskey'];

        // wechat模块 - 处理用户发送的消息和回复消息
        $this->wechatClient = new \Gaoming13\WechatPhpSdk\Wechat(array(
            'appId' => $this->appId,
            'token' => $this->appToken,
            'encodingAESKey' => $this->appEncodeAesKey //可选
        ));
        // api模块 - 包含各种系统主动发起的功能
        $this->apiClient = new \Gaoming13\WechatPhpSdk\Api(
            array(
                'appId' => $this->appId,
                'appSecret' => $this->appSecret,
                'get_access_token' => function(){
                    // 用户需要自己实现access_token的返回
                    return cache(self::WECHAT_TOKEN_CACHE_KEY);
                },
                'save_access_token' => function($token) {
                    // 用户需要自己实现access_token的保存
                    cache(self::WECHAT_TOKEN_CACHE_KEY, $token);
                }
            )
        );
    }

    public function getApiClient(){
        return $this->apiClient;
    }

    public function getWechatClient()
    {
        return $this->wechatClient;
    }

    /**
     * 获取用户授权url
     * @param $rUrl
     * @param string $state
     * @return string
     */
    public function getUserAuthUrl($rUrl, $state = '', $scope = 'snsapi_userinfo')
    {
        $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='.$this->appId.'&redirect_uri='.urlencode($rUrl).'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
        return $url;
    }

    public function getWxUserInfoByCode($code)
    {
        $res = $this->getApiClient()->get_userinfo_by_authorize('snsapi_userinfo');
        Log::info(json_encode($res));
        $wxUserInfo = null;
        if($res[1]) {
            $res[1] = json_decode(json_encode($res[1]),TRUE);
            $wxUserInfo = arrayOnly($res[1], [
                'openid',
                'nickname',
                'headimgurl'
            ]);
            if(isset($res[1]['unionid'])) {
                $wxUserInfo['unionid'] = $res[1]['unionid'];
            } else {
                $wxUserInfo['unionid'] = '';
            }
        }
        return $wxUserInfo;
    }

    public function getWxUserInfoByOpenId($openId)
    {
        $res = $this->getApiClient()->get_user_info($openId);
        $wxUserInfo = null;
        if($res[1]) {
            $res[1] = json_decode(json_encode($res[1]),TRUE);
            $wxUserInfo = arrayOnly($res[1], [
                'subscribe',
                'openid',
                'nickname',
                'headimgurl',
                'subscribe_time',
            ]);
            if(isset($res[1]['unionid'])) {
                $wxUserInfo['unionid'] = $res[1]['unionid'];
            } else {
                $wxUserInfo['unionid'] = '';
            }
        }
        return $wxUserInfo;
    }

    /**
     * 验证消息签名
     * @param $signature
     * @param $nonce
     * @param $timestamp
     * @return bool
     */
    public function checkMsgSignature($signature, $nonce, $timestamp)
    {
        $tmpArr = array($this->appToken, $timestamp, $nonce);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);

        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 消息或事件通知
     * @param $authorizerAppid
     * @param $timestamp
     * @param $nonce
     * @param $encryptType
     * @param $msgSignature
     * @param array $postData
     * @return string
     * @throws Exception
     */
    public function decryptMsg(array $postData, $timestamp, $nonce, $encryptType, $msgSignature)
    {
        $WXBizMsgCrypt = new WXBizMsgCrypt($this->appToken, $this->appEncodeAesKey, $this->appId);
        $msg = '';
        $WXBizMsgCrypt->decryptData($msgSignature, $timestamp, $nonce, $postData['Encrypt'], $msg);
        $msg = xmlToArray($msg);
        return $msg;
    }

    public function encryptMsg(array $replyMsg)
    {
        $replyMsg = arrayToXml($replyMsg, null, null, 'xml', true);
        $WXBizMsgCrypt = new WXBizMsgCrypt($this->appToken, $this->appEncodeAesKey, $this->appId);
        $timestamp = time();
        $encryptMsg = '';
        $WXBizMsgCrypt->encryptMsg($replyMsg, $timestamp, md5($replyMsg . $timestamp),$encryptMsg);
        return $encryptMsg;
    }

    /**
     * 发送客服消息
     * @param array $mesage
     * @return bool
     * @throws Exception
     */
    public function sendCustomServiceMessage(array $mesage)
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' . $this->apiClient->get_access_token();
        $result = $this->sendCmd($url, json_encode($mesage, JSON_UNESCAPED_UNICODE));
        $result = json_decode($result, true);
        if($result['errcode'] != 0) {
            Log::error('errcode:' . $result['errcode'] . ',errmsg:' . $result['errmsg']);
            throw new Exception($result['errmsg']);
        }
        return true;
    }

    /**
     * 发送订阅通知
     * @param $touser
     * @param $templateId
     * @param $page
     * @param $miniprogram
     * @param array $data
     * @throws Exception
     * @return bool|array
     */
    public function messageSubscribeBizsend($touser, $templateId, $page = null, array $miniprogram = null, array $data)
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=' . $this->apiClient->get_access_token();
        $message = [
            'touser' => $touser,
            'template_id' => $templateId,
            'data' => $data,
        ];
        if($miniprogram) {
            $message['miniprogram'] = $miniprogram;
        }
        if($page) {
            $message['page'] = $page;
        }
        $result = $this->sendCmd($url, json_encode($message, JSON_UNESCAPED_UNICODE));
        $result = json_decode($result, true);
        if($result['errcode'] != 0) {
            Log::error('errcode:' . $result['errcode'] . ',errmsg:' . $result['errmsg']);
            return $result;
        }
        return true;
    }

    /**
     * 发送模板消息
     * @param $touser
     * @param $templateId
     * @param $redirectUrl
     * @param $miniprogram
     * miniprogram:{"appid":"xiaochengxuappid12345","pagepath":"index?foo=bar"}
     * @param array $data
     * @throws Exception
     * @return bool|array
     */
    public function sendTemplateMessage($touser, $templateId, array $data, $redirectUrl = null, array $miniprogram = null)
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $this->apiClient->get_access_token();
        $message = [
            'touser' => $touser,
            'template_id' => $templateId,
            'data' => $data,
        ];
        if($miniprogram) {
            $message['miniprogram'] = $miniprogram;
        }
        if($redirectUrl) {
            $message['url'] = $redirectUrl;
        }
        $result = $this->sendCmd($url, json_encode($message, JSON_UNESCAPED_UNICODE));
        $result = json_decode($result, true);
        if($result['errcode'] != 0) {
            Log::error('errcode:' . $result['errcode'] . ',errmsg:' . $result['errmsg']);
            return $result;
        }
        return true;
    }

    public function createMenu(array $menu)
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token=' . $this->apiClient->get_access_token();
        $result = $this->sendCmd($url, json_encode($menu, JSON_UNESCAPED_UNICODE));
        $result = json_decode($result, true);
        if($result['errcode'] != 0) {
            Log::error('errcode:' . $result['errcode'] . ',errmsg:' . $result['errmsg']);
            return $result;
        }
        return true;
    }

    /**
     * 创建带参数的二维码
     * @param string $sceneStr 场景值
     * @param int $expireSeconds 该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为30秒。
     * @return mixed|string
     * @throws Exception
     */
    public function createQrCodeWithSceneStr($sceneStr, $expireSeconds = 0)
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->getApiClient()->get_access_token();
//        $expire = $expireSeconds == 0 ? '' : '"expire_seconds": ' . $expireSeconds . ',';
//        $action_name = $expireSeconds == 0 ? 'QR_LIMIT_SCENE' : 'QR_SCENE';
//        $xml = sprintf('{%s"action_name": "%s", "action_info": {"scene": {"scene_str": %s}}}',
//            $expire,
//            $action_name,
//            $sceneStr);
        $xml = [];
        if($expireSeconds > 0) {
            $xml['expire_seconds'] = $expireSeconds;
            $xml['action_name'] = 'QR_STR_SCENE';
        } else {
            $xml['action_name'] = 'QR_LIMIT_STR_SCENE';
        }
        $xml['action_info'] = [
            'scene' => [
                'scene_str' => $sceneStr
            ]
        ];

        $result = $this->sendCmd($url, json_encode($xml, JSON_UNESCAPED_UNICODE));
        $result = json_decode($result, true);
        if(isset($result['errcode']) && $result['errcode'] != 0) {
            if($result['errcode'] == 40001) {
                $this->getApiClient()->new_access_token();
            }
            Log::error('errcode:' . $result['errcode'] . ',errmsg:' . $result['errmsg']);
            throw new Exception('二维码生成失败');
        }
        return $result;
    }

    /**
     * 生成二维码场景值
     * @param array $data
     * @return string
     */
    public function genQrCodeSceneStr(array $data)
    {
        $scene = md5(microtime() . rand_string(10));
        cache('wx_qrcode_scene:' . $scene, $data, 30 * 24 * 3600);
        return $scene;
    }

    /**
     * 解析二维码场景值
     * @param $scene
     * @return mixed
     */
    public function decodeQrCodeScene($scene)
    {
        return cache('wx_qrcode_scene:' . $scene);
    }

    /**
     * 发起请求
     * @param string $url 请求地址
     * @param string $data 请求数据包
     * @return   string      请求返回数据
     * @throws
     */
    public function sendCmd($url, $data)
    {
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检测
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Expect:')); //解决数据包大不能提交
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回

        $tmpInfo = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) {
            $Errno = curl_error($curl);
            curl_close($curl);
            throw new Exception($Errno);
        }
        curl_close($curl); // 关键CURL会话
        return $tmpInfo; // 返回数据
    }
}